﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Collections;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;

namespace IntegrationPlatformMappingTool
{
    public partial class Form1 : Form
    {
        /// <summary>
        /// Holds the list of work item fields from the source work item type definition
        /// </summary>
        SortedList<string, Field> _sourceFields;
        /// <summary>
        /// Holds the list of work item fields from the target work item type definition
        /// </summary>
        SortedList<string, Field> _targetFields;
        /// <summary>
        /// The type of work item of the source (the name such as Bug or Requirement)
        /// </summary>
        string _sourceType;
        /// <summary>
        /// The type of work item of the target (the name such as Bug or Requirement)
        /// </summary>
        string _targetType;

        public Form1()
        {
            InitializeComponent();
            lvwMapping.Columns[0].Width = lvwMapping.Width / 2 - 5;
            lvwMapping.Columns[1].Width = lvwMapping.Width / 2 - 5;
        }

        /// <summary>
        /// Browse for the source work item type definition file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowseSource_Click(object sender, EventArgs e)
        {
            txtSource.Text = GetFile();
            if (txtSource.Text.Length > 0)
            {
                if (File.Exists(txtSource.Text))
                {
                    //If the file is really there and we can get to it, retrieve the fields from it
                    _sourceFields = XmlHelper.GetFields(txtSource.Text, out _sourceType);
                    //Load the listbox with the contents of the field list
                    LoadList(_sourceFields, lstSource);
                    grpSource.Text = string.Format("Source ({0})", _sourceType);
                }
            }
        }

        /// <summary>
        /// Browse for the target work item type definition file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowseTarget_Click(object sender, EventArgs e)
        {
            txtTarget.Text = GetFile();
            if (txtTarget.Text.Length > 0)
            {
                if (File.Exists(txtTarget.Text))
                {
                    //If the file is really there and we can get to it, retrieve the fields from it
                    _targetFields = XmlHelper.GetFields(txtTarget.Text, out _targetType);
                    //Load the listbox with the contents of the field list
                    LoadList(_targetFields, lstTarget);
                    grpTarget.Text = string.Format("Target ({0})", _targetType);
                }
            }
        }

        /// <summary>
        /// Perform the actual loading of the list
        /// </summary>
        /// <param name="source">The source of data for the list</param>
        /// <param name="list">The listbox to load it into</param>
        private void LoadList(SortedList<string, Field> source, ListBox list)
        {
            
            for (int i = 0; i < source.Count; i++)
            {
                //Don't add items that have been removed
                if (!((Field)source.ElementAt(i).Value).Removed)
                    list.Items.Add(source.ElementAt(i).Key);
            }
        }

        /// <summary>
        /// Allow the user to pick the file that contains the definition that they want to map
        /// </summary>
        /// <returns></returns>
        private string GetFile()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Multiselect = false;
            dlg.Filter = "Xml Files (*.xml)|*.xml|All Files (*.*)|*.*";
            dlg.ShowDialog();
            return dlg.FileName;
        }

        /// <summary>
        /// Perform an automapping between the files. This is based on the field names and nothing else
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAuto_Click(object sender, EventArgs e)
        {
            if (_sourceFields != null)
            {
                //For each value in the source work item type (the controlling one), 
                //find the each of the related fields
                for (int i = 0; i < _sourceFields.Count; i++)
                {
                    Field f;
                    string value = _sourceFields.ElementAt(i).Key;
                    if (_targetFields.TryGetValue(value, out f))
                    {
                        //Add the value to the listview
                        AddListViewItem(value, value, false);

                        //The value is in the target list so remove it from both lists
                        RemoveListItem(value, lstSource);
                        RemoveListItem(value, lstTarget);
                    }
                }
            }
        }

        /// <summary>
        /// Add the mapped fields to the listview
        /// </summary>
        /// <param name="source">The source field</param>
        /// <param name="target">The target field that the source will be mapped to</param>
        /// <param name="highlight">Indicates whether or not to highlight the listview row</param>
        private void AddListViewItem(string source, string target, bool highlight)
        {
            ListViewItem item = new ListViewItem(source);
            item.SubItems.Add(target);

            if (highlight)
            {
                item.BackColor = Color.Yellow;
                item.SubItems[0].BackColor = Color.Yellow;
            }

            lvwMapping.Items.Add(item);
        }

        /// <summary>
        /// Remove an item from the listbox
        /// </summary>
        /// <param name="value">The value to remove</param>
        /// <param name="list">The list to remove it from</param>
        private void RemoveListItem(string value, ListBox list)
        {
            for (int i = 0; i < list.Items.Count; i++)
            {
                if (list.Items[i].ToString() == value)
                {
                    list.Items.RemoveAt(i);
                    break;
                }
            }

            //Because the value is being removed from the list view, make sure to 
            //set the fact that it isn't in the source or target lists anymore so
            //if/when we need to re-load the listboxes we exclude those values
            Field f;
            if (list.Name == "lstSource")
            {
                _sourceFields.TryGetValue(value, out f);
                f.Removed = true;
            }
            else if (list.Name == "lstTarget")
            {
                _targetFields.TryGetValue(value, out f);
                f.Removed = true;
            }
        }

        /// <summary>
        /// Clear everything in case the user wants to start over again
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnClear_Click(object sender, EventArgs e)
        {
            txtSource.Text = string.Empty;
            txtTarget.Text = string.Empty;
            lstSource.Items.Clear();
            lstTarget.Items.Clear();
            lvwMapping.Items.Clear();
            _sourceFields = null;
            _targetFields = null;
        }

        /// <summary>
        /// Map the source field to the target field
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMap_Click(object sender, EventArgs e)
        {
            //Make sure the values are selected
            if ((lstSource.SelectedIndex > -1) && (lstTarget.SelectedIndex > -1))
            {
                //Retrieve the values
                string source = lstSource.Items[lstSource.SelectedIndex].ToString();
                string target = lstTarget.Items[lstTarget.SelectedIndex].ToString();

                //Determine if there is a data mismatch
                MismatchResult result = CheckForMismatch(source, target);
                bool highlight = false;

                if (result == MismatchResult.YesMismatch)
                    highlight = true;
                
                //If there is, are we going to map it anyway?
                if ((result == MismatchResult.YesNoMismatch) || (result == MismatchResult.YesMismatch))
                { 
                    //Map it and remove it from the source and target lists
                    AddListViewItem(source, target, highlight);
                    RemoveListItem(source, lstSource);
                    RemoveListItem(target, lstTarget);
                }
            }
            else
            {
                MessageBox.Show("Please select a source and a target field.", "Fiels not selected");
            }
        }

        /// <summary>
        /// Check to see if the data types of each field don't match
        /// </summary>
        /// <param name="source">The source field</param>
        /// <param name="target">The target field</param>
        /// <returns></returns>
        private MismatchResult CheckForMismatch(string source, string target)
        {
            MismatchResult mapAnyway = MismatchResult.YesNoMismatch;

            Field sourceField;
            Field targetField;

            _sourceFields.TryGetValue(source, out sourceField);
            _targetFields.TryGetValue(target, out targetField);

            if (sourceField.DataType != targetField.DataType)
            {
                DialogResult result = MessageBox.Show(string.Format("The data types of the selected fields do not match." +
                    "\nSource Field is {0} - {1}\nTarget Field is {2} - {3}\n\nDo you want to map these fields?"
                    , source, sourceField.DataType, target, targetField.DataType),
                    "Data Type Mismatch", MessageBoxButtons.YesNo);
                if (result == System.Windows.Forms.DialogResult.Yes)
                    mapAnyway = MismatchResult.YesMismatch;
                else
                    mapAnyway = MismatchResult.No;
            }

            return mapAnyway;
        }

        /// <summary>
        /// Removed the mapped fields from the listview and return them to their respective listboxes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRemove_Click(object sender, EventArgs e)
        {
            if (lvwMapping.SelectedItems.Count == 0)
            {
                MessageBox.Show("Please select an item to remove.", "No Item Selected");
            }
            else
            {
                //Get the values to return
                ListViewItem item = lvwMapping.SelectedItems[0];
                string source = item.Text;
                string target = item.SubItems[1].Text;
                lvwMapping.Items.Remove(item);

                //Mark them as returned in the source lists
                Field f1;
                Field f2;

                _sourceFields.TryGetValue(source, out f1);
                f1.Removed = false;

                _targetFields.TryGetValue(target, out f2);
                f2.Removed = false;

                //Clear and reload the listboxes
                lstSource.Items.Clear();
                LoadList(_sourceFields, lstSource);

                lstTarget.Items.Clear();
                LoadList(_targetFields, lstTarget);
            }
        }

        /// <summary>
        /// Determine where to put the output file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowseOutput_Click(object sender, EventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Filter = "Xml Files (*.xml)|*.xml|All Files (*.*)|*.*";
            dlg.FileName = "Mapping.xml";
            dlg.ShowDialog();
            txtOutput.Text = dlg.FileName;
        }

        /// <summary>
        /// Export the mappings
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExport_Click(object sender, EventArgs e)
        {
            if ((txtOutput.Text.Length > 0) && (lvwMapping.Items.Count > 0))
            {
                ProcessMapping();
            }
            else
            {
                MessageBox.Show("Please provide the location of the output file.", "Output file missing");
            }
        }

        /// <summary>
        /// Construct the mapping xml file and save it
        /// </summary>
        private void ProcessMapping()
        {
            /*
             * Create an XML file with the following structure:
             * 
               <WorkItemTypes>
                <WorkItemType LeftWorkItemTypeName="Bug" RightWorkItemTypeName="Bug" fieldMap="BugToBugFieldMap" />
              </WorkItemTypes>
              <FieldMaps>
                <FieldMap name="BugToBugFieldMap">
                  <MappedFields>
                    <MappedField MapFromSide="Left" LeftName="Microsoft.VSTS.Common.Priority" RightName="Microsoft.VSTS.Common.Priority" />
                  </MappedFields>
                </FieldMap>
              </FieldMaps>
             * */
            Cursor = Cursors.WaitCursor;
            string source = _sourceType.Replace(" ", "");
            string target = _targetType.Replace(" ", "");

            XmlTextWriter xw = new XmlTextWriter(txtOutput.Text, null);
            xw.Formatting = Formatting.Indented;

            xw.WriteComment("Replace the WorkItemTypes node and FieldMaps node in the TFS Integration Platform mapping file with the content of this file");
            try
            {
                //Create the Work Item Types node first
                xw.WriteStartElement("WorkItemTypes");
                xw.WriteStartElement("WorkItemType");
                xw.WriteAttributeString("LeftWorkItemTypeName", _sourceType);
                xw.WriteAttributeString("RightWorkItemTypeName", _targetType);
                xw.WriteAttributeString("fieldMap", string.Format("{0}To{1}FieldMap", source, target));
                xw.WriteEndElement();   //Close the Work Item Type node
                xw.WriteEndElement();   //Close the Work Item Types node

                //Write the field maps node
                xw.WriteStartElement("FieldMaps");
                xw.WriteStartElement("FieldMap");
                xw.WriteAttributeString("name", string.Format("{0}To{1}FieldMap", source, target));

                //Write the mapped fields
                xw.WriteStartElement("MappedFields");

                for (int i = 0; i < lvwMapping.Items.Count; i++)
                {
                    ListViewItem item = lvwMapping.Items[i];
                    xw.WriteStartElement("MappedField");
                    xw.WriteAttributeString("MapFromSide", "Left");
                    xw.WriteAttributeString("LeftName", GetSourceValue(item.SubItems[0].Text));
                    xw.WriteAttributeString("RightName", GetTargetValue(item.SubItems[1].Text));
                    xw.WriteEndElement();
                }

                xw.WriteEndElement();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error");
            }
            finally
            {
                xw.Close();
                Cursor = Cursors.Default;
                MessageBox.Show("Export complete", "Done");
            }
        }

        /// <summary>
        /// Gets the value from the sorted list using trygetvalue
        /// </summary>
        /// <param name="name">The key to the sorted list value</param>
        /// <returns>The reference name of the field</returns>
        private string GetSourceValue(string name)
        {
            Field f;
            _sourceFields.TryGetValue(name, out f);

            return f.ReferenceName;
        }

        /// <summary>
        /// Gets the value from the sorted list using trygetvalue
        /// </summary>
        /// <param name="name">The key to the sorted list value</param>
        /// <returns>The reference name of the field</returns>
        private string GetTargetValue(string name)
        {
            Field f;
            _targetFields.TryGetValue(name, out f);

            return f.ReferenceName;
        }

        private void btnServerSource_Click(object sender, EventArgs e)
        {
            Microsoft.TeamFoundation.Client.TeamProjectPicker tpp = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, false);
            tpp.ShowDialog();
            TfsTeamProjectCollection tpc = tpp.SelectedTeamProjectCollection;
            ProjectInfo pi = tpp.SelectedProjects[0];
            
            WorkItemTypePicker wip = new WorkItemTypePicker(pi, tpc, WitLocation.Source);
            wip.WorkItemSelected += new WorkItemTypePicker.WorkItemSelectedHandler(wip_WorkItemSelected);
            wip.ShowDialog();
            wip.WorkItemSelected -= new WorkItemTypePicker.WorkItemSelectedHandler(wip_WorkItemSelected);
        }

        void wip_WorkItemSelected(object sender, WorkItemPickerClosedEventArgs e)
        {
            TextBox t;
            ListBox l;
            SortedList<string, Field> list;
            string workitemname;

            if (e.WitLocation == WitLocation.Source)
            {
                t = txtSource;
                l = lstSource;
                list = _sourceFields;
            }
            else
            {
                t = txtTarget;
                l = lstTarget;
                list = _targetFields;
            }
            t.Text = e.WorkItem;
            list = XmlHelper.ReadData(e.WITD, out workitemname);

            if (e.WitLocation == WitLocation.Source)
            {
                _sourceFields = list;
                _sourceType = workitemname;
            }
            else
            {
                _targetFields = list;
                _targetType = workitemname;
            }

            LoadList(list, l);
        }

        private void btnServerTarget_Click(object sender, EventArgs e)
        {
            Microsoft.TeamFoundation.Client.TeamProjectPicker tpp = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, false);
            tpp.ShowDialog();
            TfsTeamProjectCollection tpc = tpp.SelectedTeamProjectCollection;
            ProjectInfo pi = tpp.SelectedProjects[0];

            WorkItemTypePicker wip = new WorkItemTypePicker(pi, tpc, WitLocation.Target);
            wip.WorkItemSelected += new WorkItemTypePicker.WorkItemSelectedHandler(wip_WorkItemSelected);
            wip.ShowDialog();
            wip.WorkItemSelected -= new WorkItemTypePicker.WorkItemSelectedHandler(wip_WorkItemSelected);
        }
    }
}
